/// Copyright (C) 2013 Michael Leviston (michael.leviston@gmail.com)

/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:

/// The above copyright notice and this permission notice shall be included in
/// all copies or substantial portions of the Software.

/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
/// THE SOFTWARE.

#include "World.h"

namespace ECS
{

	World::World()
		: eID(1), cID(1) 
	{
	}

	World::~World()
	{
		Shutdown();
	}

	wpEntity World::GetEntity(unsigned int EntityID)
	{
		wpEntity wpE;

		auto itF = entities.find(EntityID);
		if(itF != entities.end() )
			wpE = itF->second;
		else
			wpE;
		return wpE;
	}

	void World::Update(float dt)
	{
		UpdateSystems(dt);
		RemoveComponents();
		RemoveEntities();
	}

	void World::UpdateSystems(float dt)
	{
		std::for_each( systems.begin(), systems.end(), [dt] ( std::pair<int, spSystem> pis )
		{
			pis.second->Update(dt);
		} );
	}


	void World::RemoveComponents()
	{
		for(auto it = markedComponents.begin(); it != markedComponents.end(); ++it)
		{
			RemoveComponent(it->first, it->second);
		}

		markedComponents.clear();
	}

	void World::RemoveEntities()
	{
		auto itB = markedEntities.begin();
		auto itE = markedEntities.end();

		for(itB; itB != itE; ++itB)
		{
			RemoveEntity(*itB);
		}

		markedEntities.clear();
	}

	bool World::MarkEntity(unsigned int EntityID)
	{
		auto itF = entities.find(EntityID);
		if(itF != entities.end() )
		{
			itF->second->MarkForRemoval();
			markedEntities.insert(EntityID);
			return true;
		}

		return false;
	}

	bool World::MarkComponent(unsigned int EntityID, unsigned int ComponentID)
	{
		auto itF = entities.find(EntityID);
		if(itF != entities.end() )
		{
			auto itB = itF->second->GetComponents()->begin();
			auto itE = itF->second->GetComponents()->end();

			for(itB; itB != itE; ++itB)
			{
				if(itB->second->GetID() == ComponentID)
				{
					itB->second->MarkForRemoval();
					markedComponents.insert(std::pair<int,int>(EntityID, ComponentID) );
					return true;
				}
			}
		}

		return false;
	}

	void World::Shutdown()
	{
		entities.clear();
		systems.clear();
	}

	void World::AddComponent(unsigned int EntityID, spComponent spC)
	{
		auto itF = entities.find(EntityID);
		if(itF != entities.end() )
		{
			if( itF->second->AddComponent(spC) )
			{
				spC->SetParentEntityID(EntityID);

				if(unusedComponentIDs.size() > 0)
				{
					unsigned int compID = *unusedComponentIDs.begin();
					spC->SetID(compID);
					unusedComponentIDs.erase(compID);
				}
				else
				{
					spC->SetID(cID++);
				}
			}
		}
	}

	unsigned int World::AddEntity()
	{
		unsigned int entityID = 0;

		if(unusedEntityIDs.size() > 0)
		{
			entityID = *unusedEntityIDs.begin();
			spEntity spE(new Entity(entityID) );
			entities[entityID] = spE;
			unusedEntityIDs.erase(entityID);
		}
		else
		{
			spEntity spE(new Entity(eID++) );
			entityID = spE->GetID();
			entities[entityID] = spE;
		}

		return entityID;
	}

	void World::ActivateEntity(unsigned int EntityID)
	{
		auto itF = entities.find(EntityID);
		if(itF != entities.end() )
		{
			// all systems make nodes
			for(auto itB = systems.begin(); itB != systems.end(); ++itB)
			{
				itB->second->MakeNode( itF->second );
			}
		}
	}

	void World::AddSystem(spSystem spS)
	{
		systems[spS->GetPriority()] = spS;
	}

	void World::RemoveComponent(unsigned int EntityID, unsigned int ComponentID)
	{
		auto itF = entities.find(EntityID);
		if(itF != entities.end() )
		{
			if(itF->second->RemoveComponentByID(ComponentID))
			{
				// Deregister Component from Systems
				for(auto itB = systems.begin(); itB != systems.end(); ++itB)
				{
					if(itB->second->HasComponentID(ComponentID) )
					{
						itB->second->DeleteGenericNode(ComponentID);
					}
				}

				// Add the cID to the unused list
				unusedComponentIDs.insert(ComponentID);
			}

			// If the entity is empty, remove it also
			if(itF->second->GetComponents()->size() == 0)
			{
				itF->second->MarkForRemoval();
				markedEntities.insert(EntityID);
			}
		}
	}

	void World::RemoveEntity(unsigned int EntityID)
	{
		auto itF = entities.find(EntityID);
		if(itF != entities.end() )
		{
			// Go through components associated with the entity
			auto itB = itF->second->GetComponents()->begin();
			auto itE = itF->second->GetComponents()->end();

			for(itB; itB != itE; ++itB)
			{
				int ComponentID = itB->second->GetID();	
				{
					// Deregister components
					for(auto it = systems.begin(); it != systems.end(); ++it)
					{
						if(it->second->HasComponentID(ComponentID) )
						{
							it->second->DeleteGenericNode(ComponentID);
						}
					}

					// Add each ID back into the unused pool
					unusedComponentIDs.insert(ComponentID);
				}
			}

			// Remove all components
			itF->second->ClearComponents();

			// Remove the entity
			entities.erase(EntityID);

			// Add eID to unused list
			unusedEntityIDs.insert(EntityID);
		}
	}
}